#include "vd.h"

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/bio.h>
#include <linux/slab.h>

#define VD_MSG_PREFIX "target"

struct tt_internal {
    struct target_type tt;

    struct list_head list;
    long use;
};

static LIST_HEAD(_targets);
static DECLARE_RWSEM(_lock);

static inline struct tt_internal *__find_target_type(const char *name)
{
    struct tt_internal *ti;

    list_for_each_entry (ti, &_targets, list)
        if (!strcmp(name, ti->tt.name))
            return ti;

    return NULL;
}

static struct tt_internal *get_target_type(const char *name)
{
    struct tt_internal *ti;

    down_read(&_lock);

    ti = __find_target_type(name);
    if (ti) {
        if ((ti->use == 0) && !try_module_get(ti->tt.module))
            ti = NULL;
        else
            ti->use++;
    }

    up_read(&_lock);
    return ti;
}

static void load_module(const char *name)
{
    request_module("vd-%s", name);

    return;
}

struct target_type *vd_get_target_type(const char *name)
{
    struct tt_internal *ti = get_target_type(name);

    if (!ti) {
        load_module(name);
        ti = get_target_type(name);
    }

    return ti ? &ti->tt : NULL;
}

void vd_put_target_type(struct target_type *t)
{
    struct tt_internal *ti = (struct tt_internal *) t;

    down_read(&_lock);
    if (--ti->use == 0)
        module_put(ti->tt.module);

    BUG_ON(ti->use < 0);
    up_read(&_lock);

    return;
}

static struct tt_internal *alloc_target(struct target_type *t)
{
    struct tt_internal *ti = kmalloc(sizeof(*ti), GFP_KERNEL);

    if (ti) {
        memset(ti, 0, sizeof(*ti));
        ti->tt = *t;
    }

    return ti;
}

int vd_register_target(struct target_type *t)
{
    int rv = 0;
    struct tt_internal *ti = alloc_target(t);

    if (!ti)
        return -ENOMEM;

    down_write(&_lock);
    if (__find_target_type(t->name))
        rv = -EEXIST;
    else
        list_add(&ti->list, &_targets);

    up_write(&_lock);
    if (rv)
        kfree(ti);
    
    return rv;
}

int vd_unregister_target(struct target_type *t)
{
    struct tt_internal *ti;

    down_write(&_lock);
    if (!(ti = __find_target_type(t->name))) {
        up_write(&_lock);
        return -EINVAL;
    }

    if (ti->use) {
        up_write(&_lock);
        return -ETXTBSY;
    }

    list_del(&ti->list);
    kfree(ti);

    up_write(&_lock);
    
    return 0;
}
